/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// models.c -- model loading and caching

// models are the only shared resource between a client and server running
// on the same machine.

#include "quakedef.h"

model_t	*loadmodel;
char	loadname[32];	// for hunk tags

void Mod_LoadSpriteModel (model_t *mod, void *buffer);
void Mod_LoadBrushModel (model_t *mod, void *buffer);
void Mod_LoadAliasModel (model_t *mod, void *buffer);
model_t *Mod_LoadModel (model_t *mod, qboolean crash);

#define	MAX_MOD_KNOWN	512
byte	mod_novis[MAX_MAP_LEAFS / 8];
model_t	mod_known[MAX_MOD_KNOWN];
int		mod_numknown;

/*
===============
Mod_Init
===============
*/
void Mod_Init (void)
{
    Cvar_RegisterVariable (&gl_subdivide_size, NULL);
    memset (mod_novis, 0xff, sizeof(mod_novis));
}

/*
===============
Mod_Init

Caches the data if needed
===============
*/
void *Mod_Extradata (model_t *mod)
{
    void	*r;

    r = Cache_Check (&mod->cache);

    if (r)
    {
        return r;
    }
    Mod_LoadModel (mod, true);

    if (!mod->cache.data)
    {
        if (mod->type != mod_alias)
        {
            Con_Printf ("Mod_Extradata: %s is not an alias model", mod->name);
            return NULL;
        }
        Sys_Error ("Mod_Extradata: caching failed for %s", mod->name);
    }
    return mod->cache.data;
}

/*
===============
Mod_PointInLeaf
===============
*/
mleaf_t *Mod_PointInLeaf (vec3_t p, model_t *model)
{
    mnode_t		*node;
    float		d;
    mplane_t	*plane;

    if (!model || !model->nodes)
    {
        Sys_Error ("Mod_PointInLeaf: bad model");
    }
    node = model->nodes;

    while (1)
    {
        if (node->contents < 0)
        {
            return (mleaf_t *)node;
        }
        plane = node->plane;
        d = DotProduct (p,plane->normal) - plane->dist;

        if (d > 0)
        {
            node = node->children[0];
        }
        else
        {
            node = node->children[1];
        }
    }
    return NULL;	// never reached
}


/*
===================
Mod_DecompressVis
===================
*/
byte *Mod_DecompressVis (byte *in, model_t *model)
{
    static	byte	decompressed[MAX_MAP_LEAFS/8];
    int		c;
    byte	*out;
    int		row;

    row = (model->numleafs+7)>>3;
    out = decompressed;

    if (!in)
    {
        // no vis info, so make all visible
        while (row)
        {
            *out++ = 0xff;
            row--;
        }
        return decompressed;
    }

    do
    {
        if (*in)
        {
            *out++ = *in++;
            continue;
        }

        c = in[1];
        in += 2;
        while (c)
        {
            *out++ = 0;
            c--;
        }
    }
    while (out - decompressed < row);

    return decompressed;
}

byte *Mod_LeafPVS (mleaf_t *leaf, model_t *model)
{
    if (leaf == model->leafs)
    {
        return mod_novis;
    }
    return Mod_DecompressVis (leaf->compressed_vis, model);
}

/*
===================
Mod_ClearAll
===================
*/
void Mod_ClearAll (void)
{
    int		i;
    model_t	*mod;

    for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
    {
        if (mod->type != mod_alias)
        {
            mod->needload = true;
        }
    }
}

/*
==================
Mod_FindName

==================
*/
model_t *Mod_FindName (char *name)
{
    int		i;
    model_t	*mod;

    if (!name[0])
    {
        Sys_Error ("Mod_ForName: NULL name");
    }

//
// search the currently loaded models
//
    for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
    {
        if (!strcmp (mod->name, name) ) break;
    }

    if (i == mod_numknown)
    {
        if (mod_numknown == MAX_MOD_KNOWN)
        {
            Sys_Error ("mod_numknown == MAX_MOD_KNOWN");
        }
        strcpy (mod->name, name);
        mod->needload = true;
        mod_numknown++;
    }
    return mod;
}

/*
==================
Mod_TouchModel

==================
*/
void Mod_TouchModel (char *name)
{
    model_t	*mod;

    mod = Mod_FindName (name);

    if (!mod->needload)
    {
        if (mod->type == mod_alias)
        {
            Cache_Check (&mod->cache);
        }
    }
}

/*
==================
Mod_LoadModel

Loads a model into the cache
==================
*/
model_t *Mod_LoadModel (model_t *mod, qboolean crash)
{
    void		*d;
    unsigned	*buf;
    byte		stackbuf[1024];		// avoid dirtying the cache heap

    if (!mod->needload)
    {
        if (mod->type == mod_alias)
        {
            d = Cache_Check (&mod->cache);

            if (d)
            {
                return mod;
            }
        }
        else
        {
            return mod;		// not cached at all
        }
    }

//
// because the world is so huge, load it one piece at a time
//
    if (!crash)
    {

    }

//
// load the file
//
    //PENTA: Some teamfortress maps refer to names starting with / this is not the correct
    //quake convention but somehow they got away with it...
    if (mod->name[0] != '/')
    {
        buf = (unsigned *)COM_LoadStackFile (mod->name, stackbuf, sizeof(stackbuf));
    }
    else
    {
        buf = (unsigned *)COM_LoadStackFile ((mod->name+1), stackbuf, sizeof(stackbuf));
    }

    if (!buf)
    {
        if (crash)
        {
            Sys_Error ("Mod_NumForName: %s not found", mod->name);
        }
        return NULL;
    }

//
// allocate a new model
//
    COM_FileBase (mod->name, loadname);

    loadmodel = mod;

//
// fill it in
//

// call the apropriate loader
    mod->needload = false;

    switch (LittleLong(*(unsigned *)buf))
    {
        //XYZ
    case MD3_IDENT:
        Mod_LoadMd3Model (mod, buf);
        break;

    case IDPOLYHEADER:
        Mod_LoadAliasModel (mod, buf);
        break;

    case IDSPRITEHEADER:
        Mod_LoadSpriteModel (mod, buf);
        break;

    default:
        Mod_LoadBrushModel (mod, buf);
        break;
    }
    return mod;
}

/*
==================
Mod_ForName

Loads in a model for the given name
==================
*/
model_t *Mod_ForName (char *name, qboolean crash)
{
    model_t	*mod;

    mod = Mod_FindName (name);

    return Mod_LoadModel (mod, crash);
}


/*
===============================================================================

					BRUSHMODEL LOADING

===============================================================================
*/

byte	*mod_base;

/*
=================
Mod_LoadTextures
=================
*/
void Mod_LoadTextures (lump_t *l)
{
    int		        i, j, pixels, num, max, altmax;
    miptex_t	    *mt;
    texture_t	    *tx, *tx2;
    texture_t	    *anims[10];
    texture_t	    *altanims[10];
    dmiptexlump_t   *m;

    if (!l->filelen)
    {
        loadmodel->textures = NULL;
        return;
    }
    m = (dmiptexlump_t *)(mod_base + l->fileofs);
    m->nummiptex = LittleLong (m->nummiptex);

    loadmodel->numtextures = m->nummiptex;
    loadmodel->textures = (texture_t **) Hunk_AllocName (m->nummiptex * sizeof(*loadmodel->textures), loadname);

    for (i=0 ; i<m->nummiptex ; i++)
    {
        m->dataofs[i] = LittleLong(m->dataofs[i]);
        if (m->dataofs[i] == -1) continue;
        mt = (miptex_t *)((byte *)m + m->dataofs[i]);
        mt->width = LittleLong (mt->width);
        mt->height = LittleLong (mt->height);

        for (j=0 ; j<MIPLEVELS ; j++)
        {
            mt->offsets[j] = LittleLong (mt->offsets[j]);
        }

        if ( (mt->width & 15) || (mt->height & 15) )
        {
            Sys_Error ("Texture %s is not 16 aligned", mt->name);
        }
        pixels = mt->width * mt->height / 64 * 85;
        tx = Hunk_AllocName (sizeof(texture_t) + pixels, loadname);
        loadmodel->textures[i] = tx;

        memcpy (tx->name, mt->name, sizeof(tx->name));
        tx->width = mt->width;
        tx->height = mt->height;

        for (j=0 ; j<MIPLEVELS ; j++)
        {
            tx->offsets[j] = mt->offsets[j] + sizeof(texture_t) - sizeof(miptex_t);
        }

        // the pixels immediately follow the structures
        memcpy (tx + 1, mt + 1, pixels);

        if (ISSKYTEX(mt->name))
        {
            R_InitSky (tx);
        }
        else
        {
            if (cls.state == ca_dedicated) continue; //Don't load textures on a dedicated server

            tx->gl_texturenum = GL_LoadTexture (mt->name, tx->width, tx->height, (byte *)(tx + 1), TEX_MIPMAP|TEX_BUMP);

            if (!ISTURBTEX(mt->name))
            {
                tx->gl_lumatexturenum = GL_LoadLumaTexture (mt->name, 0, 0, TEX_ALPHA|TEX_LUMA);
            }
        }
    }

//
// sequence the animations
//
    for (i=0 ; i<m->nummiptex ; i++)
    {
        tx = loadmodel->textures[i];
        if (!tx || tx->name[0] != '+') continue;
        if (tx->anim_next) continue;	// allready sequenced

        // find the number of frames in the animation
        memset (anims, 0, sizeof(anims));
        memset (altanims, 0, sizeof(altanims));

        max = tx->name[1];
        altmax = 0;

        if (max >= 'a' && max <= 'z')
        {
            max -= 'a' - 'A';
        }

        if (max >= '0' && max <= '9')
        {
            max -= '0';
            altmax = 0;
            anims[max] = tx;
            max++;
        }
        else if (max >= 'A' && max <= 'J')
        {
            altmax = max - 'A';
            max = 0;
            altanims[altmax] = tx;
            altmax++;
        }
        else
        {
            Sys_Error ("Bad animating texture %s", tx->name);
        }

        for (j=i+1 ; j<m->nummiptex ; j++)
        {
            tx2 = loadmodel->textures[j];
            if (!tx2 || tx2->name[0] != '+') continue;
            if (strcmp (tx2->name+2, tx->name+2)) continue;

            num = tx2->name[1];

            if (num >= 'a' && num <= 'z')
            {
                num -= 'a' - 'A';
            }
            if (num >= '0' && num <= '9')
            {
                num -= '0';
                anims[num] = tx2;

                if (num+1 > max)
                {
                    max = num + 1;
                }
            }
            else if (num >= 'A' && num <= 'J')
            {
                num = num - 'A';
                altanims[num] = tx2;

                if (num+1 > altmax)
                {
                    altmax = num+1;
                }
            }
            else
            {
                Sys_Error ("Bad animating texture %s", tx->name);
            }
        }

#define	ANIM_CYCLE	2
        // link them all together
        for (j=0 ; j<max ; j++)
        {
            tx2 = anims[j];

            if (!tx2)
            {
                Sys_Error ("Missing frame %i of %s",j, tx->name);
            }
            tx2->anim_total = max * ANIM_CYCLE;
            tx2->anim_min = j * ANIM_CYCLE;
            tx2->anim_max = (j+1) * ANIM_CYCLE;
            tx2->anim_next = anims[ (j+1)%max ];

            if (altmax)
            {
                tx2->alternate_anims = altanims[0];
            }
        }

        for (j=0 ; j<altmax ; j++)
        {
            tx2 = altanims[j];

            if (!tx2)
            {
                Sys_Error ("Missing frame %i of %s",j, tx->name);
            }
            tx2->anim_total = altmax * ANIM_CYCLE;
            tx2->anim_min = j * ANIM_CYCLE;
            tx2->anim_max = (j+1) * ANIM_CYCLE;
            tx2->anim_next = altanims[ (j+1)%altmax ];

            if (max)
            {
                tx2->alternate_anims = anims[0];
            }
        }
    }
}

/*
=================
Mod_LoadLighting
=================
*/
void Mod_LoadLighting (lump_t *l)
{
    if (!l->filelen)
    {
        loadmodel->lightdata = NULL;
        return;
    }
    loadmodel->lightdata = (byte *) Hunk_AllocName (l->filelen, loadname);
    memcpy (loadmodel->lightdata, mod_base + l->fileofs, l->filelen);
}


/*
=================
Mod_LoadVisibility
=================
*/
void Mod_LoadVisibility (lump_t *l)
{
    if (!l->filelen)
    {
        loadmodel->visdata = NULL;
        return;
    }
    loadmodel->visdata = (byte *) Hunk_AllocName (l->filelen, loadname);
    memcpy (loadmodel->visdata, mod_base + l->fileofs, l->filelen);
}


/*
=================
Mod_LoadEntities
=================
*/
void Mod_LoadEntities (lump_t *l)
{
    FILE	*f;
    char	filename[MAX_OSPATH];
    int     flength;

    /* restrict to gamedir */
    va_snprintf(filename, MAX_OSPATH, "maps/%s.edo", loadname);
    flength = COM_FOpenFile (va("%s", filename, com_gamedir), &f);

    if (f)
    {
        Con_Printf("Loading entities from edo file %s path %s\n", filename);
        loadmodel->entities = (char *) Hunk_AllocName (flength, loadname);
        fread(loadmodel->entities, 1, flength, f);
        fclose(f);
        return;
    }

    if (!l->filelen)
    {
        loadmodel->entities = NULL;
        return;
    }
    loadmodel->entities = (char *) Hunk_AllocName (l->filelen, loadname);
    memcpy (loadmodel->entities, mod_base + l->fileofs, l->filelen);
}


/*
=================
Mod_LoadVertexes
=================
*/
void Mod_LoadVertexes (lump_t *l)
{
    dvertex_t	*in;
    mvertex_t	*out;
    int			i, count;

    in = (dvertex_t *)(mod_base + l->fileofs);

    if (l->filelen % sizeof(*in))
    {
        Sys_Error ("MOD_LoadBmodel: funny lump size in %s", loadmodel->name);
    }
    count = l->filelen / sizeof(*in);
    out = (mvertex_t *) Hunk_AllocName ( count*sizeof(*out), loadname);

    loadmodel->vertexes = out;
    loadmodel->numvertexes = count;

    for ( i=0 ; i<count ; i++, in++, out++)
    {
        out->position[0] = LittleFloat (in->point[0]);
        out->position[1] = LittleFloat (in->point[1]);
        out->position[2] = LittleFloat (in->point[2]);
    }
}

/*
=================
Mod_LoadSubmodels
=================
*/
void Mod_LoadSubmodels (lump_t *l)
{
    dmodel_t	*in;
    dmodel_t	*out;
    int			i, j, count;

    in = (dmodel_t *)(mod_base + l->fileofs);

    if (l->filelen % sizeof(*in))
    {
        Sys_Error ("MOD_LoadBmodel: funny lump size in %s", loadmodel->name);
    }
    count = l->filelen / sizeof(*in);
    out = (dmodel_t *) Hunk_AllocName ( count*sizeof(*out), loadname);

    loadmodel->submodels = out;
    loadmodel->numsubmodels = count;

    for (i=0 ; i<count ; i++, in++, out++)
    {
        for (j=0 ; j<3 ; j++)
        {
            // spread the mins / maxs by a pixel
            out->mins[j] = LittleFloat (in->mins[j]) - 1;
            out->maxs[j] = LittleFloat (in->maxs[j]) + 1;
            out->origin[j] = LittleFloat (in->origin[j]);
        }

        for (j=0 ; j<MAX_MAP_HULLS ; j++)
        {
            out->headnode[j] = LittleLong (in->headnode[j]);
        }
        out->visleafs = LittleLong (in->visleafs);
        out->firstface = LittleLong (in->firstface);
        out->numfaces = LittleLong (in->numfaces);
    }
}

/*
=================
Mod_LoadEdges
=================
*/
void Mod_LoadEdges (lump_t *l)
{
    dedge_t *in;
    medge_t *out;
    int 	i, count;

    in = (dedge_t *)(mod_base + l->fileofs);

    if (l->filelen % sizeof(*in))
    {
        Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
    }
    count = l->filelen / sizeof(*in);
    out = (medge_t *) Hunk_AllocName ( (count + 1) * sizeof(*out), loadname);

    loadmodel->edges = out;
    loadmodel->numedges = count;

    for ( i=0 ; i<count ; i++, in++, out++)
    {
        out->v[0] = (unsigned short)LittleShort(in->v[0]);
        out->v[1] = (unsigned short)LittleShort(in->v[1]);
    }
}

/*
=================
Mod_LoadTexinfo
=================
*/
void Mod_LoadTexinfo (lump_t *l)
{
    texinfo_t	*in;
    mtexinfo_t	*out;
    int 		i, j, count;
    int			miptex;
    float		len1, len2;

    in = (texinfo_t *)(mod_base + l->fileofs);

    if (l->filelen % sizeof(*in))
    {
        Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
    }
    count = l->filelen / sizeof(*in);
    out = (mtexinfo_t *) Hunk_AllocName (count * sizeof(*out), loadname);

    loadmodel->texinfo = out;
    loadmodel->numtexinfo = count;

    for ( i=0 ; i<count ; i++, in++, out++)
    {
        for (j=0 ; j<8 ; j++)
        {
            out->vecs[0][j] = LittleFloat (in->vecs[0][j]);
        }
        len1 = Length (out->vecs[0]);
        len2 = Length (out->vecs[1]);
        len1 = (len1 + len2) * 0.5f;

        if (len1 < 0.32)
        {
            out->mipadjust = 4;
        }
        else if (len1 < 0.49)
        {
            out->mipadjust = 3;
        }
        else if (len1 < 0.99)
        {
            out->mipadjust = 2;
        }
        else
        {
            out->mipadjust = 1;
        }
        miptex = LittleLong (in->miptex);
        out->flags = LittleLong (in->flags);

        if (!loadmodel->textures)
        {
            out->texture = r_notexture_mip;	// checkerboard texture
            out->flags = 0;
        }
        else
        {
            if (miptex >= loadmodel->numtextures)
            {
                Sys_Error ("miptex >= loadmodel->numtextures");
            }
            out->texture = loadmodel->textures[miptex];

            if (!out->texture)
            {
                out->texture = r_notexture_mip; // texture not found
                out->flags = 0;
            }
        }
    }
}

/*
================
Mod_CalcSurfaceExtents

Fills in s->texturemins[] and s->extents[]
================
*/
void Mod_CalcSurfaceExtents (msurface_t *s)
{
    float		mins[2], maxs[2], val;
    int			i, j, e;
    mvertex_t	*v;
    mtexinfo_t	*tex;
    int			bmins[2], bmaxs[2];

    mins[0] = mins[1] = 999999;
    maxs[0] = maxs[1] = -99999;

    tex = s->texinfo;

    for (i=0 ; i<s->numedges ; i++)
    {
        e = loadmodel->surfedges[s->firstedge + i];

        if (e >= 0)
        {
            v = &loadmodel->vertexes[loadmodel->edges[e].v[0]];
        }
        else
        {
            v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]];
        }

        for (j=0 ; j<2 ; j++)
        {
            val = v->position[0] * tex->vecs[j][0] +
                  v->position[1] * tex->vecs[j][1] +
                  v->position[2] * tex->vecs[j][2] +
                  tex->vecs[j][3];

            if (val < mins[j])
            {
                mins[j] = val;
            }

            if (val > maxs[j])
            {
                maxs[j] = val;
            }
        }
    }

    for (i=0 ; i<2 ; i++)
    {
        bmins[i] = floor(mins[i]/16);
        bmaxs[i] = ceil(maxs[i]/16);

        s->texturemins[i] = bmins[i] * 16;
        s->extents[i] = (bmaxs[i] - bmins[i]) * 16;

        if (!(tex->flags & TEX_SPECIAL) && s->extents[i] > 2000)
        {
            Sys_Error ("Bad surface extents");
        }
    }
}


/*
=================
Mod_LoadFaces
=================
*/
void Mod_LoadFaces (lump_t *l)
{
    dface_t		*in;
    msurface_t 	*out;
    int			i, count, surfnum;
    int			planenum, side;

    in = (dface_t *)(mod_base + l->fileofs);

    if (l->filelen % sizeof(*in))
    {
        Sys_Error ("MOD_LoadBmodel: funny lump size in %s", loadmodel->name);
    }
    count = l->filelen / sizeof(*in);
    out = (msurface_t *) Hunk_AllocName (count * sizeof(*out), loadname);

    loadmodel->surfaces = out;
    loadmodel->numsurfaces = count;

    for ( surfnum=0 ; surfnum<count ; surfnum++, in++, out++)
    {
        out->firstedge = LittleLong(in->firstedge);
        out->numedges = LittleShort(in->numedges);

        out->flags = 0;

        planenum = LittleShort(in->planenum);
        side = LittleShort(in->side);

        if (side)
        {
            out->flags |= SURF_PLANEBACK;
        }
        out->plane = loadmodel->planes + planenum;

        // Quake's normal is used for BSP tree traversal, but does not take backfacing
        // into account, and so is inappropriate for OpenGL use.  Here we calculate one that does.
        if (side)
        {
            out->truenormal[0] = out->plane->normal[0] * -1;
            out->truenormal[1] = out->plane->normal[1] * -1;
            out->truenormal[2] = out->plane->normal[2] * -1;
        }
        else
        {
            out->truenormal[0] = out->plane->normal[0];
            out->truenormal[1] = out->plane->normal[1];
            out->truenormal[2] = out->plane->normal[2];
        }

        // make it unit length
        Normalize (out->truenormal);

        out->texinfo = loadmodel->texinfo + LittleShort (in->texinfo);

        Mod_CalcSurfaceExtents (out);

        // lighting info
        for (i=0 ; i<MAXLIGHTMAPS ; i++)
        {
            out->styles[i] = in->styles[i];
        }
        i = LittleLong(in->lightofs);

        if (i == -1)
        {
            out->samples = NULL;
        }
        else
        {
            out->samples = loadmodel->lightdata + i;
        }

        // set the drawing flags (tenebrae had some of these in the turb block but neither mirror nor glass nor caulk has a turb surface...)
        if (ISMIRRORTEX(out->texinfo->texture->name))           // mirrors
        {
            out->flags |= SURF_MIRROR;
        }
        else if (ISGLASSTEX(out->texinfo->texture->name))       // glass
        {
            out->flags |= SURF_GLASS;
        }
        else if (ISCAULKTEX(out->texinfo->texture->name))       // caulk
        {
            out->flags |= SURF_CAULK;
        }
        else if (ISSKYTEX(out->texinfo->texture->name))	        // sky
        {
            out->flags |= (SURF_DRAWSKY | SURF_DRAWTILED);
        }
        else if (ISTURBTEX(out->texinfo->texture->name))		// turbulent
        {
            out->flags |= (SURF_DRAWTURB | SURF_DRAWTILED);

            for (i=0 ; i<2 ; i++)
            {
                out->extents[i] = 16384;
                out->texturemins[i] = -8192;
            }
            GL_SubdivideSurface (out);	// cut up polygon for warps

            // set the content types here (might not match names in some mods so expect weirdness).
            if (strstr (out->texinfo->texture->name, "tele") || strstr (out->texinfo->texture->name, "slip"))   // teleport or slipgate
            {
                out->flags |= SURF_TELE;
            }
            else if (strstr (out->texinfo->texture->name, "lava"))                                              // lava
            {
                out->flags |= SURF_LAVA;
            }
            else if (strstr (out->texinfo->texture->name, "slime"))                                             // slime
            {
                out->flags |= SURF_SLIME;
            }
            else                                                                                                // water
            {
                out->flags |= SURF_WATER;
            }

            if (out->flags & (SURF_MIRROR & SURF_WATER))                                                        // mirror water
            {
                out->flags |= (SURF_MIRROR & SURF_WATER);
            }

            if (out->flags & (SURF_MIRROR & SURF_GLASS))                                                        // mirror haze
            {
                out->flags |= (SURF_MIRROR & SURF_GLASS);
            }
        }
    }
}

/*
=================
Mod_SetParent
=================
*/
void Mod_SetParent (mnode_t *node, mnode_t *parent)
{
    node->parent = parent;
    if (node->contents < 0) return;
    Mod_SetParent (node->children[0], node);
    Mod_SetParent (node->children[1], node);
}

/*
=================
Mod_LoadNodes
=================
*/
void Mod_LoadNodes (lump_t *l)
{
    int			i, j, count, p;
    dnode_t		*in;
    mnode_t 	*out;

    in = (dnode_t *)(mod_base + l->fileofs);

    if (l->filelen % sizeof(*in))
    {
        Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
    }
    count = l->filelen / sizeof(*in);
    out = (mnode_t *) Hunk_AllocName (count * sizeof(*out), loadname);

    loadmodel->nodes = out;
    loadmodel->numnodes = count;

    for ( i=0 ; i<count ; i++, in++, out++)
    {
        for (j=0 ; j<3 ; j++)
        {
            out->minmaxs[j] = LittleShort (in->mins[j]);
            out->minmaxs[3 + j] = LittleShort (in->maxs[j]);
        }
        p = LittleLong(in->planenum);
        out->plane = loadmodel->planes + p;

        out->firstsurface = LittleShort (in->firstface);
        out->numsurfaces = LittleShort (in->numfaces);

        for (j=0 ; j<2 ; j++)
        {
            p = LittleShort (in->children[j]);

            if (p >= 0)
            {
                out->children[j] = loadmodel->nodes + p;
            }
            else
            {
                out->children[j] = (mnode_t *)(loadmodel->leafs + (-1 - p));
            }
        }
    }
    Mod_SetParent (loadmodel->nodes, NULL);	// sets nodes and leafs
}

/*
=================
Mod_LoadLeafs
=================
*/
void Mod_LoadLeafs (lump_t *l)
{
    dleaf_t 	*in;
    mleaf_t 	*out;
    int			i, j, count, countc, p;

    in = (dleaf_t *)(mod_base + l->fileofs);

    if (l->filelen % sizeof(*in))
    {
        Sys_Error ("MOD_LoadBmodel: funny lump size in %s", loadmodel->name);
    }
    count = l->filelen / sizeof(*in);
    out = Hunk_AllocName ( count*sizeof(*out), loadname);

    loadmodel->leafs = out;
    loadmodel->numleafs = count;

    for (i=0 ; i<count ; i++, in++, out++)
    {
        for (j=0 ; j<3 ; j++)
        {
            out->minmaxs[j] = LittleShort (in->mins[j]);
            out->minmaxs[3+j] = LittleShort (in->maxs[j]);
        }
        p = LittleLong(in->contents);
        out->contents = p;

        out->firstmarksurface = loadmodel->marksurfaces +  LittleShort(in->firstmarksurface);
        out->nummarksurfaces = LittleShort(in->nummarksurfaces);

        p = LittleLong(in->visofs);

        if (p == -1)
        {
            out->compressed_vis = NULL;
        }
        else
        {
            out->compressed_vis = loadmodel->visdata + p;
        }
        out->efrags = NULL;

        for (j=0 ; j<4 ; j++)
        {
            out->ambient_sound_level[j] = in->ambient_level[j];
        }

        // gl underwater warp
        if (CONTENTMASK_LIQUID(out))
        {
            for (j=0 ; j<out->nummarksurfaces ; j++)
            {
                out->firstmarksurface[j]->flags |= SURF_UNDERWATER;
            }
        }
        countc = out->nummarksurfaces;

        for (j=0; j<out->nummarksurfaces; j++)
        {
            if (out->firstmarksurface[j]->flags & SURF_CAULK)
            {
                out->firstmarksurface[j] = NULL;
            }
        }
        countc = 0;

        for (j=0; j<out->nummarksurfaces; j++)
        {
            if (out->firstmarksurface[j] != NULL)
            {
                out->firstmarksurface[countc] = out->firstmarksurface[j];
                countc++;
            }
        }
        out->nummarksurfaces = countc;

        //PENTA: is key used somewhere, and what does it do
        //searching for key revealed nothing so i use it as the index of this leaf
        out->index = i;
    }
}

/*
=================
Mod_LoadClipnodes
=================
*/
void Mod_LoadClipnodes (lump_t *l)
{
    dclipnode_t *in, *out;
    int			i, count;
    hull_t		*hull;

    in = (dclipnode_t *)(mod_base + l->fileofs);

    if (l->filelen % sizeof(*in))
    {
        Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
    }
    count = l->filelen / sizeof(*in);
    out = (dclipnode_t *) Hunk_AllocName ( count*sizeof(*out), loadname);

    loadmodel->clipnodes = out;
    loadmodel->numclipnodes = count;

    hull = &loadmodel->hulls[1];
    hull->clipnodes = out;
    hull->firstclipnode = 0;
    hull->lastclipnode = count-1;
    hull->planes = loadmodel->planes;
    hull->clip_mins[0] = -16;
    hull->clip_mins[1] = -16;
    hull->clip_mins[2] = -24;
    hull->clip_maxs[0] = 16;
    hull->clip_maxs[1] = 16;
    hull->clip_maxs[2] = 32;

    hull = &loadmodel->hulls[2];
    hull->clipnodes = out;
    hull->firstclipnode = 0;
    hull->lastclipnode = count-1;
    hull->planes = loadmodel->planes;
    hull->clip_mins[0] = -32;
    hull->clip_mins[1] = -32;
    hull->clip_mins[2] = -24;
    hull->clip_maxs[0] = 32;
    hull->clip_maxs[1] = 32;
    hull->clip_maxs[2] = 64;

    for (i=0 ; i<count ; i++, out++, in++)
    {
        out->planenum = LittleLong(in->planenum);
        out->children[0] = LittleShort(in->children[0]);
        out->children[1] = LittleShort(in->children[1]);
    }
}

/*
=================
Mod_MakeHull0

Deplicate the drawing hull structure as a clipping hull
=================
*/
void Mod_MakeHull0 (void)
{
    mnode_t		*in, *child;
    dclipnode_t *out;
    int			i, j, count;
    hull_t		*hull;

    hull = &loadmodel->hulls[0];

    in = loadmodel->nodes;
    count = loadmodel->numnodes;
    out = (dclipnode_t *) Hunk_AllocName (count * sizeof(*out), loadname);

    hull->clipnodes = out;
    hull->firstclipnode = 0;
    hull->lastclipnode = count-1;
    hull->planes = loadmodel->planes;

    for (i=0 ; i<count ; i++, out++, in++)
    {
        out->planenum = in->plane - loadmodel->planes;

        for (j=0 ; j<2 ; j++)
        {
            child = in->children[j];

            if (child->contents < 0)
            {
                out->children[j] = child->contents;
            }
            else
            {
                out->children[j] = child - loadmodel->nodes;
            }
        }
    }
}

/*
=================
Mod_LoadMarksurfaces
=================
*/
void Mod_LoadMarksurfaces (lump_t *l)
{
    int			i, j, count;
    short		*in;
    msurface_t **out;

    in = (short *)(mod_base + l->fileofs);

    if (l->filelen % sizeof(*in))
    {
        Sys_Error ("MOD_LoadBmodel: funny lump size in %s", loadmodel->name);
    }
    count = l->filelen / sizeof(*in);
    out = (msurface_t **) Hunk_AllocName (count * sizeof(*out), loadname);

    loadmodel->marksurfaces = out;
    loadmodel->nummarksurfaces = count;

    for ( i=0 ; i<count ; i++)
    {
        j = LittleShort(in[i]);

        if (j >= loadmodel->numsurfaces)
        {
            Sys_Error ("Mod_ParseMarksurfaces: bad surface number");
        }
        out[i] = loadmodel->surfaces + j;
    }
}

/*
=================
Mod_LoadSurfedges
=================
*/
void Mod_LoadSurfedges (lump_t *l)
{
    int		i, count;
    int		*in, *out;

    in = (int *)(mod_base + l->fileofs);

    if (l->filelen % sizeof(*in))
    {
        Sys_Error ("MOD_LoadBmodel: funny lump size in %s", loadmodel->name);
    }
    count = l->filelen / sizeof(*in);
    out = (int *) Hunk_AllocName ( count*sizeof(*out), loadname);

    loadmodel->surfedges = out;
    loadmodel->numsurfedges = count;

    for (i=0 ; i<count ; i++)
    {
        out[i] = LittleLong (in[i]);
    }
}


/*
=================
Mod_LoadPlanes
=================
*/
void Mod_LoadPlanes (lump_t *l)
{
    int			i, j;
    mplane_t	*out;
    dplane_t 	*in;
    int			count;
    int			bits;

    in = (dplane_t *)(mod_base + l->fileofs);

    if (l->filelen % sizeof(*in))
    {
        Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name);
    }
    count = l->filelen / sizeof(*in);
    out = (mplane_t *) Hunk_AllocName (count * 2 * sizeof(*out), loadname);

    loadmodel->planes = out;
    loadmodel->numplanes = count;

    for ( i=0 ; i<count ; i++, in++, out++)
    {
        bits = 0;

        for (j=0 ; j<3 ; j++)
        {
            out->normal[j] = LittleFloat (in->normal[j]);

            if (out->normal[j] < 0)
            {
                bits |= 1 << j;
            }
        }
        out->dist = LittleFloat (in->dist);
        out->type = LittleLong (in->type);
        out->signbits = bits;
    }
}

/*
=================
Mod_RadiusFromBounds
=================
*/
float Mod_RadiusFromBounds (vec3_t mins, vec3_t maxs)
{
    int		i;
    vec3_t	corner;

    for (i=0 ; i<3 ; i++)
    {
        corner[i] = fabs(mins[i]) > fabs(maxs[i]) ? fabs(mins[i]) : fabs(maxs[i]);
    }
    return Length (corner);
}

/*
=================
Mod_LoadBrushModel
=================
*/
void Mod_LoadBrushModel (model_t *mod, void *buffer)
{
    int			i, j;
    dheader_t	*header;
    dmodel_t 	*bm;

    loadmodel->type = mod_brush;

    header = (dheader_t *)buffer;

    i = LittleLong (header->version);

    if (i != BSPVERSION)
    {
        Sys_Error ("Mod_LoadBrushModel: %s has wrong version number (%i should be %i)", mod->name, i, BSPVERSION);
    }

// swap all the lumps
    mod_base = (byte *)header;

    for (i = 0 ; i < (int) sizeof(dheader_t) / 4 ; i++)
    {
        ((int *)header)[i] = LittleLong ( ((int *)header)[i]);
    }

// load into heap
    Con_DPrintf("Vertexes\n");
    Mod_LoadVertexes (&header->lumps[LUMP_VERTEXES]);
    Con_DPrintf("Edges\n");
    Mod_LoadEdges (&header->lumps[LUMP_EDGES]);
    Con_DPrintf("SurfEdges\n");
    Mod_LoadSurfedges (&header->lumps[LUMP_SURFEDGES]);
    Con_DPrintf("Textures\n");
    Mod_LoadTextures (&header->lumps[LUMP_TEXTURES]);
    Con_DPrintf("Lighting\n");
    Mod_LoadLighting (&header->lumps[LUMP_LIGHTING]);
    Con_DPrintf("Planes\n");
    Mod_LoadPlanes (&header->lumps[LUMP_PLANES]);
    Con_DPrintf("Texinfo\n");
    Mod_LoadTexinfo (&header->lumps[LUMP_TEXINFO]);
    Con_DPrintf("Faces\n");
    Mod_LoadFaces (&header->lumps[LUMP_FACES]);
    Con_DPrintf("MarkSurfaces\n");
    Mod_LoadMarksurfaces (&header->lumps[LUMP_MARKSURFACES]);
    Con_DPrintf("Visibility\n");
    Mod_LoadVisibility (&header->lumps[LUMP_VISIBILITY]);
    Con_DPrintf("Leafs\n");
    Mod_LoadLeafs (&header->lumps[LUMP_LEAFS]);
    Con_DPrintf("Nodes\n");
    Mod_LoadNodes (&header->lumps[LUMP_NODES]);
    Con_DPrintf("ClipNodes\n");
    Mod_LoadClipnodes (&header->lumps[LUMP_CLIPNODES]);
    Con_DPrintf("Entities\n");
    Mod_LoadEntities (&header->lumps[LUMP_ENTITIES]);
    Con_DPrintf("Submodels\n");
    Mod_LoadSubmodels (&header->lumps[LUMP_MODELS]);

    Mod_MakeHull0 ();

    mod->numframes = 2;		// regular and alternate animation

//
// set up the submodels (FIXME: this is confusing)
//
    for (i=0 ; i<mod->numsubmodels ; i++)
    {
        bm = &mod->submodels[i];

        mod->hulls[0].firstclipnode = bm->headnode[0];

        for (j=1 ; j<MAX_MAP_HULLS ; j++)
        {
            mod->hulls[j].firstclipnode = bm->headnode[j];
            mod->hulls[j].lastclipnode = mod->numclipnodes-1;
        }
        mod->firstmodelsurface = bm->firstface;
        mod->nummodelsurfaces = bm->numfaces;

        VectorCopy (bm->maxs, mod->maxs);
        VectorCopy (bm->mins, mod->mins);

        mod->radius = Mod_RadiusFromBounds (mod->mins, mod->maxs);

        mod->numleafs = bm->visleafs;

        if (i < mod->numsubmodels-1)
        {
            // duplicate the basic information
            char	name[10];

            sprintf (name, "*%i", i+1);
            loadmodel = Mod_FindName (name);
            *loadmodel = *mod;
            strcpy (loadmodel->name, name);
            loadmodel->type = mod_brush;
            mod = loadmodel;
        }
    }
}

/*
==============================================================================

ALIAS MODELS

==============================================================================
*/

aliashdr_t	*pheader;

stvert_t	stverts[MAXALIASVERTS];
mtriangle_t	triangles[MAXALIASTRIS];

// a pose is a single set of vertexes.  a frame may be
// an animating sequence of poses
trivertx_t	*poseverts[MAXALIASFRAMES];
int			posenum;

byte		**player_8bit_texels_tbl;
byte		*player_8bit_texels;

int	aliasbboxmins[3], aliasbboxmaxs[3];

/*
=================
Mod_LoadAliasFrame
=================
*/

void * Mod_LoadAliasFrame (void * pin, maliasframedesc_t *frame)
{
    trivertx_t		*pinframe;
    int				i;
    daliasframe_t	*pdaliasframe;

    pdaliasframe = (daliasframe_t *)pin;
    strcpy (frame->name, pdaliasframe->name);
    frame->firstpose = posenum;
    frame->numposes = 1;

    // these are byte values, so we don't have to worry about
    // endianness
    for (i=0 ; i<3 ; i++)
    {
        frame->bboxmin.v[i] = pdaliasframe->bboxmin.v[i];
        frame->bboxmax.v[i] = pdaliasframe->bboxmax.v[i];

        aliasbboxmins[i] = min (frame->bboxmin.v[i], aliasbboxmins[i]);
        aliasbboxmaxs[i] = max (frame->bboxmax.v[i], aliasbboxmaxs[i]);
    }
    pinframe = (trivertx_t *)(pdaliasframe + 1);
    poseverts[posenum] = pinframe;
    posenum++;
    pinframe += pheader->numverts;

    return (void *)pinframe;
}


/*
=================
Mod_LoadAliasGroup
=================
*/
void *Mod_LoadAliasGroup (void * pin,  maliasframedesc_t *frame)
{
    daliasgroup_t		*pingroup;
    int					i, numframes;
    daliasinterval_t	*pin_intervals;
    void				*ptemp;

    pingroup = (daliasgroup_t *)pin;

    numframes = LittleLong (pingroup->numframes);

    frame->firstpose = posenum;
    frame->numposes = numframes;

    for (i=0 ; i<3 ; i++)
    {
        // these are byte values, so we don't have to worry about endianness
        frame->bboxmin.v[i] = pingroup->bboxmin.v[i];
        frame->bboxmax.v[i] = pingroup->bboxmax.v[i];

        aliasbboxmins[i] = min (frame->bboxmin.v[i], aliasbboxmins[i]);
        aliasbboxmaxs[i] = max (frame->bboxmax.v[i], aliasbboxmaxs[i]);
    }
    pin_intervals = (daliasinterval_t *)(pingroup + 1);
    frame->interval = LittleFloat (pin_intervals->interval);
    pin_intervals += numframes;
    ptemp = (void *)pin_intervals;

    for (i=0 ; i<numframes ; i++)
    {
        poseverts[posenum] = (trivertx_t *)((daliasframe_t *)ptemp + 1);
        posenum++;
        ptemp = (trivertx_t *)((daliasframe_t *)ptemp + 1) + pheader->numverts;
    }
    return ptemp;
}

//=========================================================

/*
=================
Mod_FloodFillSkin

Fill background pixels so mipmapping doesn't have haloes - Ed
=================
*/

typedef struct
{
    short		x, y;
} floodfill_t;

// must be a power of 2
#define FLOODFILL_FIFO_SIZE 0x1000
#define FLOODFILL_FIFO_MASK (FLOODFILL_FIFO_SIZE - 1)

#define FLOODFILL_STEP( off, dx, dy ) \
{ \
	if (pos[off] == fillcolor) \
	{ \
		pos[off] = 255; \
		fifo[inpt].x = x + (dx), fifo[inpt].y = y + (dy); \
		inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; \
	} \
	else if (pos[off] != 255) fdc = pos[off]; \
}

void Mod_FloodFillSkin( byte *skin, int skinwidth, int skinheight )
{
    byte				fillcolor = *skin; // assume this is the pixel to fill
    floodfill_t			fifo[FLOODFILL_FIFO_SIZE];
    int					inpt = 0, outpt = 0;
    int					filledcolor = -1;
    int					i;

    if (filledcolor == -1)
    {
        filledcolor = 0;
        // attempt to find opaque black
        for (i = 0; i < 256; ++i)
        {
            if (d_8to24table[i] == (255 << 0)) // alpha 1.0
            {
                filledcolor = i;
                break;
            }
        }
    }

    // can't fill to filled color or to transparent color (used as visited marker)
    if ((fillcolor == filledcolor) || (fillcolor == 255))
    {
        //printf( "not filling skin from %d to %d\n", fillcolor, filledcolor );
        return;
    }
    fifo[inpt].x = 0, fifo[inpt].y = 0;
    inpt = (inpt + 1) & FLOODFILL_FIFO_MASK;

    while (outpt != inpt)
    {
        int			x = fifo[outpt].x, y = fifo[outpt].y;
        int			fdc = filledcolor;
        byte		*pos = &skin[x + skinwidth * y];

        outpt = (outpt + 1) & FLOODFILL_FIFO_MASK;

        if (x > 0)				FLOODFILL_STEP( -1, -1, 0 );
        if (x < skinwidth - 1)	FLOODFILL_STEP( 1, 1, 0 );
        if (y > 0)				FLOODFILL_STEP( -skinwidth, 0, -1 );
        if (y < skinheight - 1)	FLOODFILL_STEP( skinwidth, 0, 1 );
        skin[x + skinwidth * y] = fdc;
    }
}

/*
===============
Mod_LoadAllSkins
===============
*/
void *Mod_LoadAllSkins (int numskins, daliasskintype_t *pskintype)
{
    int		                i, j, k;
    char	                name[32];
    int		                s;
    byte	                *texels;
    daliasskingroup_t		*pinskingroup;
    int		                groupskins;
    daliasskininterval_t	*pinskinintervals;

    if (numskins < 1 || numskins > MAX_SKINS)
    {
        Sys_Error ("Mod_LoadAliasModel: Invalid # of skins: %d\n", numskins);
    }
    s = pheader->skinwidth * pheader->skinheight;

    for (i=0 ; i<numskins ; i++)
    {
        if (pskintype->type == ALIAS_SKIN_SINGLE)
        {
            Mod_FloodFillSkin((byte *)(pskintype + 1), pheader->skinwidth, pheader->skinheight);

            texels = (byte *) Hunk_AllocName(s, loadname);
            pheader->texels[i] = texels - (byte *)pheader;
            memcpy (texels, (byte *)(pskintype + 1), s);

            va_snprintf (name, sizeof(name), "%s_%i", loadmodel->name, i);
            pheader->gl_texturenum[i][0] =
                pheader->gl_texturenum[i][1] =
                    pheader->gl_texturenum[i][2] =
                        pheader->gl_texturenum[i][3] = GL_LoadTexture (name, pheader->skinwidth, pheader->skinheight, (byte *)(pskintype + 1), TEX_MIPMAP|TEX_BUMP);

            pheader->gl_lumatexturenum[0][0] =
                pheader->gl_lumatexturenum[0][1] =
                    pheader->gl_lumatexturenum[0][2] =
                        pheader->gl_lumatexturenum[0][3] = GL_LoadLumaTexture (name, 0, 0, TEX_ALPHA|TEX_LUMA);

            pskintype = (daliasskintype_t *)((byte *)(pskintype + 1) + s);
        }
        else
        {
            // animating skin group.  yuck.
            pskintype++;
            pinskingroup = (daliasskingroup_t *)pskintype;
            groupskins = LittleLong (pinskingroup->numskins);
            pinskinintervals = (daliasskininterval_t *)(pinskingroup + 1);

            pskintype = (daliasskintype_t *)(pinskinintervals + groupskins);

            for (j=0 ; j<groupskins ; j++)
            {
                Mod_FloodFillSkin((byte *)(pskintype), pheader->skinwidth, pheader->skinheight);

                if (j == 0)
                {
                    texels = (byte *) Hunk_AllocName(s, loadname);
                    pheader->texels[i] = texels - (byte *)pheader;
                    memcpy (texels, (byte *)(pskintype), s);
                }
                va_snprintf (name, sizeof(name), "%s_%i_%i", loadmodel->name, i,j);
                pheader->gl_texturenum[i][j & 3] = GL_LoadTexture (name, pheader->skinwidth, pheader->skinheight, (byte *)(pskintype), TEX_ALPHA|TEX_MIPMAP|TEX_BUMP);
                pheader->gl_texturenum[i][j & 3] = GL_LoadLumaTexture (name, 0, 0, TEX_ALPHA|TEX_LUMA);
                pskintype = (daliasskintype_t *)((byte *)(pskintype) + s);
            }

            for (k = j; j < 4; j++)
            {
                pheader->gl_texturenum[i][j & 3] = pheader->gl_texturenum[i][j - k];
            }
        }
    }
    return (void *)pskintype;
}

//=========================================================================

/*
=================
Mod_LoadAliasModel
=================
*/
void Mod_LoadAliasModel (model_t *mod, void *buffer)
{
    int					i, j;
    mdl_t				*pinmodel;
    stvert_t			*pinstverts;
    dtriangle_t			*pintriangles;
    int					version, numframes;
    int					size;
    daliasframetype_t	*pframetype;
    daliasskintype_t	*pskintype;
    int					start, end, total;
    alias3data_t		*palias3;

    start = Hunk_LowMark ();
    pinmodel = (mdl_t *)buffer;
    version = LittleLong (pinmodel->version);

    if (version != ALIAS_VERSION)
    {
        Sys_Error ("%s has wrong version number (%i should be %i)", mod->name, version, ALIAS_VERSION);
    }
    size = sizeof (alias3data_t);
    palias3 = (alias3data_t *) Hunk_AllocName (size, loadname);
    palias3->numSurfaces = 1;

//
// allocate space for a working header, plus all the data except the frames,
// skin and group info
//
    size = sizeof (aliashdr_t) + (LittleLong (pinmodel->numframes) - 1) * sizeof (pheader->frames[0]);
    pheader = (aliashdr_t *) Hunk_AllocName (size, loadname);

    palias3->ofsSurfaces[0] = (int)((char *)pheader - (char *)palias3);

    mod->flags = LittleLong (pinmodel->flags);

//
// endian-adjust and copy the data, starting with the alias model header
//
    pheader->ident = MD3_IDENT;
    pheader->boundingradius = LittleFloat (pinmodel->boundingradius);
    pheader->numskins = LittleLong (pinmodel->numskins);
    pheader->skinwidth = LittleLong (pinmodel->skinwidth);
    pheader->skinheight = LittleLong (pinmodel->skinheight);

    if (pheader->skinheight > MAX_LBM_HEIGHT)
    {
        Sys_Error ("model %s has a skin taller than %d", mod->name, MAX_LBM_HEIGHT);
    }
    pheader->numverts = LittleLong (pinmodel->numverts);

    if (pheader->numverts <= 0)
    {
        Sys_Error ("model %s has no vertices", mod->name);
    }

    if (pheader->numverts > MAXALIASVERTS)
    {
        Sys_Error ("model %s has too many vertices", mod->name);
    }
    pheader->numtris = LittleLong (pinmodel->numtris);

    if (pheader->numtris <= 0)
    {
        Sys_Error ("model %s has no triangles", mod->name);
    }

    if (pheader->numtris > MAXALIASTRIS)
    {
        Sys_Error ("model %s has too many triangles", mod->name);
    }
    pheader->numframes = LittleLong (pinmodel->numframes);
    numframes = pheader->numframes;

    if (numframes < 1)
    {
        Sys_Error ("Mod_LoadAliasModel: Invalid # of frames: %d\n", numframes);
    }
    pheader->size = LittleFloat (pinmodel->size) * ALIAS_BASE_SIZE_RATIO;
    mod->synctype = LittleLong (pinmodel->synctype);
    mod->numframes = pheader->numframes;

    for (i=0 ; i<3 ; i++)
    {
        pheader->scale[i] = LittleFloat (pinmodel->scale[i]);
        pheader->scale_origin[i] = LittleFloat (pinmodel->scale_origin[i]);
        pheader->eyeposition[i] = LittleFloat (pinmodel->eyeposition[i]);
    }


//
// load the skins
//
    pskintype = (daliasskintype_t *)&pinmodel[1];
    pskintype = Mod_LoadAllSkins (pheader->numskins, pskintype);

//
// load base s and t vertices
//
    pinstverts = (stvert_t *)pskintype;

    for (i=0 ; i<pheader->numverts ; i++)
    {
        stverts[i].onseam = LittleLong (pinstverts[i].onseam);
        stverts[i].s = LittleLong (pinstverts[i].s);
        stverts[i].t = LittleLong (pinstverts[i].t);
    }

//
// load triangle lists
//
    pintriangles = (dtriangle_t *)&pinstverts[pheader->numverts];

    for (i=0 ; i<pheader->numtris ; i++)
    {
        triangles[i].facesfront = LittleLong (pintriangles[i].facesfront);

        for (j=0 ; j<3 ; j++)
        {
            triangles[i].vertindex[j] = LittleLong (pintriangles[i].vertindex[j]);
        }
    }

//
// load the frames
//
    posenum = 0;
    pframetype = (daliasframetype_t *)&pintriangles[pheader->numtris];

    aliasbboxmins[0] = aliasbboxmins[1] = aliasbboxmins[2] =  99999;
    aliasbboxmaxs[0] = aliasbboxmaxs[1] = aliasbboxmaxs[2] = -99999;

    for (i=0 ; i<numframes ; i++)
    {
        aliasframetype_t	frametype;

        frametype = (aliasframetype_t) LittleLong (pframetype->type);

        if (frametype == ALIAS_SINGLE)
        {
            pframetype = (daliasframetype_t *) Mod_LoadAliasFrame (pframetype + 1, &pheader->frames[i]);
        }
        else
        {
            pframetype = (daliasframetype_t *) Mod_LoadAliasGroup (pframetype + 1, &pheader->frames[i]);
        }
    }
    pheader->numposes = posenum;

    mod->type = mod_alias;

// FIXME: do this right
    for (i = 0; i < 3; i++)
    {
        mod->mins[i] = aliasbboxmins[i] * pheader->scale[i] + pheader->scale_origin[i];
        mod->maxs[i] = aliasbboxmaxs[i] * pheader->scale[i] + pheader->scale_origin[i];

        pheader->mins[i] = mod->mins[i];
        pheader->maxs[i] = mod->maxs[i];

    }
    mod->radius = Mod_RadiusFromBounds (mod->mins, mod->maxs);

    //
    // build the draw lists
    //
    GL_MakeAliasModelDisplayLists (mod, pheader);

    if (!strcmp (mod->name, "progs/flame2.mdl")
            || !strcmp (mod->name, "progs/flame.mdl")
            || !strcmp (mod->name, "progs/lavaball.mdl")
            || !strcmp (mod->name, "progs/laser.mdl")
            || !strcmp (mod->name, "progs/k_spike.mdl")
            || !strcmp (mod->name, "progs/bolt.mdl")
            || !strcmp (mod->name, "progs/bolt2.mdl")
            || !strcmp (mod->name, "progs/bolt3.mdl"))
    {
        mod->flags |= EF_FULLBRIGHT;
    }

    if (!strcmp (mod->name, "progs/flame2.mdl")
            || !strcmp (mod->name, "progs/flame.mdl")
            || !strcmp (mod->name, "progs/laser.mdl")
            || !strcmp (mod->name, "progs/bolt.mdl")
            || !strcmp (mod->name, "progs/bolt2.mdl")
            || !strcmp (mod->name, "progs/bolt3.mdl"))
    {
        mod->flags |= EF_NOSHADOW;
    }
    else
    {
        mod->flags &= ~EF_NOSHADOW;
    }
//
// move the complete, relocatable alias model to the cache
//
    end = Hunk_LowMark ();
    total = end - start;

    Cache_Alloc (&mod->cache, total, loadname);
    if (!mod->cache.data) return;
    memcpy (mod->cache.data, palias3, total);

    Hunk_FreeToLowMark (start);
}

//=============================================================================

/*
=================
Mod_LoadSpriteFrame
=================
*/
void * Mod_LoadSpriteFrame (void * pin, mspriteframe_t **ppframe, int framenum)
{
    dspriteframe_t		*pinframe;
    mspriteframe_t		*pspriteframe;
    int					width, height, size, origin[2];
    char				name[64];

    pinframe = (dspriteframe_t *)pin;

    width = LittleLong (pinframe->width);
    height = LittleLong (pinframe->height);
    size = width * height;

    pspriteframe = (mspriteframe_t *) Hunk_AllocName (sizeof (mspriteframe_t),loadname);

    Q_memset (pspriteframe, 0, sizeof (mspriteframe_t));

    *ppframe = pspriteframe;

    pspriteframe->width = width;
    pspriteframe->height = height;
    origin[0] = LittleLong (pinframe->origin[0]);
    origin[1] = LittleLong (pinframe->origin[1]);

    pspriteframe->up = origin[1];
    pspriteframe->down = origin[1] - height;
    pspriteframe->left = origin[0];
    pspriteframe->right = width + origin[0];

    _snprintf (name, sizeof(name), "%s_%i", loadmodel->name, framenum);

    pspriteframe->gl_texturenum = GL_LoadTexture (name, width, height, (byte *)(pinframe + 1), TEX_ALPHA|TEX_MIPMAP);

    return (void *)((byte *)pinframe + sizeof (dspriteframe_t) + size);
}


/*
=================
Mod_LoadSpriteGroup
=================
*/
void * Mod_LoadSpriteGroup (void * pin, mspriteframe_t **ppframe, int framenum)
{
    dspritegroup_t		*pingroup;
    mspritegroup_t		*pspritegroup;
    int					i, numframes;
    dspriteinterval_t	*pin_intervals;
    float				*poutintervals;
    void				*ptemp;

    pingroup = (dspritegroup_t *)pin;

    numframes = LittleLong (pingroup->numframes);

    pspritegroup = (mspritegroup_t *) Hunk_AllocName (sizeof (mspritegroup_t) + (numframes - 1) * sizeof (pspritegroup->frames[0]), loadname);

    pspritegroup->numframes = numframes;

    *ppframe = (mspriteframe_t *)pspritegroup;

    pin_intervals = (dspriteinterval_t *)(pingroup + 1);

    poutintervals = (float *) Hunk_AllocName (numframes * sizeof (float), loadname);

    pspritegroup->intervals = poutintervals;

    for (i=0 ; i<numframes ; i++)
    {
        *poutintervals = LittleFloat (pin_intervals->interval);

        if (*poutintervals <= 0.0)
        {
            Sys_Error ("Mod_LoadSpriteGroup: interval<=0");
        }
        poutintervals++;
        pin_intervals++;
    }
    ptemp = (void *)pin_intervals;

    for (i=0 ; i<numframes ; i++)
    {
        ptemp = Mod_LoadSpriteFrame (ptemp, &pspritegroup->frames[i], framenum * 100 + i);
    }
    return ptemp;
}


/*
=================
Mod_LoadSpriteModel
=================
*/
void Mod_LoadSpriteModel (model_t *mod, void *buffer)
{
    int					i;
    int					version;
    dsprite_t			*pin;
    msprite_t			*psprite;
    int					numframes;
    int					size;
    dspriteframetype_t	*pframetype;
    char				stringbuff[MAX_OSPATH],filename[MAX_OSPATH];
    FILE				*f;

    pin = (dsprite_t *)buffer;

    version = LittleLong (pin->version);

    if (version != SPRITE_VERSION)
    {
        Sys_Error ("%s has wrong version number (%i should be %i)", mod->name, version, SPRITE_VERSION);
    }
    numframes = LittleLong (pin->numframes);

    size = sizeof (msprite_t) +	(numframes - 1) * sizeof (psprite->frames);

    psprite = (msprite_t *) Hunk_AllocName (size, loadname);

    mod->cache.data = psprite;

    psprite->type = LittleLong (pin->type);

    //PENTA: Overriden sprites have other types
    //See if we can find a file, if so then mark it overriden
    sprintf(stringbuff, "%s_0", mod->name);
    GL_GetOverrideName(stringbuff, "", filename);
    COM_FindFile (filename, NULL, &f);

    if (f)
    {
        //mark it as overriden
        psprite->type = psprite->type + 5;
        fclose(f);
    }
    psprite->maxwidth = LittleLong (pin->width);
    psprite->maxheight = LittleLong (pin->height);
    psprite->beamlength = LittleFloat (pin->beamlength);
    mod->synctype = (synctype_t) LittleLong (pin->synctype);
    psprite->numframes = numframes;

    mod->mins[0] = mod->mins[1] = -psprite->maxwidth/2;
    mod->maxs[0] = mod->maxs[1] = psprite->maxwidth/2;
    mod->mins[2] = -psprite->maxheight/2;
    mod->maxs[2] = psprite->maxheight/2;

//
// load the frames
//
    if (numframes < 1)
    {
        Sys_Error ("Mod_LoadSpriteModel: Invalid # of frames: %d\n", numframes);
    }
    mod->numframes = numframes;

    pframetype = (dspriteframetype_t *)(pin + 1);

    for (i=0 ; i<numframes ; i++)
    {
        spriteframetype_t	frametype;

        frametype = (spriteframetype_t) LittleLong (pframetype->type);
        psprite->frames[i].type = frametype;

        if (frametype == SPR_SINGLE)
        {
            pframetype = (dspriteframetype_t *) Mod_LoadSpriteFrame (pframetype + 1, &psprite->frames[i].frameptr, i);
        }
        else
        {
            pframetype = (dspriteframetype_t *) Mod_LoadSpriteGroup (pframetype + 1, &psprite->frames[i].frameptr, i);
        }
    }
    mod->type = mod_sprite;
}

//=============================================================================

/*
================
Mod_Print
================
*/
void Mod_Print (void)
{
    int		i;
    model_t	*mod;

    Con_Printf ("Cached models:\n");

    for (i=0, mod=mod_known ; i < mod_numknown ; i++, mod++)
    {
        Con_Printf ("%8p : %s\n",mod->cache.data, mod->name);
    }
}
